package org.springblade.modules.admin.service.feishu.impl;

import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lark.oapi.Client;
import com.lark.oapi.service.bitable.v1.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.admin.dto.feishu.FeiShuBatchDTO;
import org.springblade.modules.admin.dto.feishu.FeiShuFieldsDTO;
import org.springblade.modules.admin.dto.feishu.FeiShuUpdateFieldsDTO;
import org.springblade.modules.admin.utils.FeiShuUtils;
import org.springblade.modules.admin.vo.feishu.FeiShuQueryFieldsVO;
import org.springframework.stereotype.Service;
import org.springblade.modules.admin.service.feishu.IFeiShuDocumentService;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: Sharry
 * @createTime: 2023-12-27 14:06
 * @version: Version-1.0
 */
@Service
@Slf4j
public class FeiShuDocumentImpl implements IFeiShuDocumentService {

	public final int MAX_SIZE = 500;

	@Override
	public String commandAddBatchLimit(String appToken, String tableId, FeiShuBatchDTO feiShuBatchDTO) {
		// 1. 参数准备、校验
		List<?> records = feiShuBatchDTO.getRecords();
		if (Func.isEmpty(records)) {
			return "records is empty!";
		}
		String result = "";
		// 2. 当 records 条数 大于 500 时，进行分批处理：每 500 条进行一次新增操作
		int totalSize = records.size();
		int batchSize = MAX_SIZE;
		for (int startIndex = 0; startIndex < totalSize; startIndex += batchSize) {
			int endIndex = Math.min(startIndex + batchSize, totalSize);
			List<?> batch = records.subList(startIndex, endIndex);
			// 3. 执行新增操作
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(batch);
			result = this.commandAddBatch(appToken, tableId, JSONObject.toJSONString(tempFeiShuBatchDTO));
		}
		return result;
	}

	@Override
	public String commandDeleteBatchLimit(String appToken, String tableId, FeiShuBatchDTO feiShuBatchDTO) {
		// 1. 参数准备、校验
		List<?> records = feiShuBatchDTO.getRecords();
		if (Func.isEmpty(records)) {
			return "records is empty!";
		}
		String result = "";
		int totalSize = records.size();
		// 2. 当 records 条数 大于 500 时，进行分批处理：每 500 条进行一次删除操作
		int batchSize = MAX_SIZE;
		for (int startIndex = 0; startIndex < totalSize; startIndex += batchSize) {
			int endIndex = Math.min(startIndex + batchSize, totalSize);
			List<?> batch = records.subList(startIndex, endIndex);
			// 3. 执行删除操作
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(batch);
			result = this.commandDeleteBatch(appToken, tableId, JSONObject.toJSONString(tempFeiShuBatchDTO));
		}
		return result;
	}

	@Override
	public String commandUpdateBatchLimit(String appToken, String tableId, FeiShuBatchDTO feiShuBatchDTO) {
		// 1. 参数准备、校验
		List<?> records = feiShuBatchDTO.getRecords();
		if (Func.isEmpty(records)) {
			return "records is empty!";
		}
		String result = "";
		// 2. 当 records 条数 大于 500 时，进行分批处理：每 500 条进行一次更新操作
		int totalSize = records.size();
		int batchSize = MAX_SIZE;
		for (int startIndex = 0; startIndex < totalSize; startIndex += batchSize) {
			int endIndex = Math.min(startIndex + batchSize, totalSize);
			List<?> batch = records.subList(startIndex, endIndex);
			// 3. 执行更新操作
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(batch);
			result = this.commandUpdateBatch(appToken, tableId, JSONObject.toJSONString(tempFeiShuBatchDTO));
		}
		return result;
	}

	/**
	 * 通用批量更新接口, 根据 key 比较更新
	 *
	 * @param appToken       飞书应用对应文件夹的 token
	 * @param tableId        具体表格 id
	 * @param feiShuBatchDTO 飞书批量 DTO 对象
	 * @param key            比较更新的 key
	 * @return 响应结果
	 */
	@Override
	public String commandCompareUpdateBatch(String appToken, String tableId, FeiShuBatchDTO feiShuBatchDTO, String key) {
		// 0. 参数校验, appToken、tableId、key 必须有值, 否则直接return
		if (Func.isNull(appToken) || Func.isNull(tableId) || Func.isNull(key) || Func.isNull(feiShuBatchDTO)) {
			return "appToken、tableId、key must not be null!";
		}
		// 0.1 records 为空, 说明全量删除, 处理数据并 调用 commandDeleteBatchLimit
		List<?> records = feiShuBatchDTO.getRecords();
		if (Func.isEmpty(records)) {
			// 0.1.1 查询全部数据
			List<JSONObject> jsonObjects = this.commandQueryBatch(appToken, tableId, null);
			// 0.1.2 遍历并获取 jsonObjects 的 封装成 FeiShuBatchDTO 对象
			List<String> r = new ArrayList<>();
			jsonObjects.forEach(j -> r.add(j.getString("record_id")));
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(r);
			// 0.1.3 调用 commandDeleteBatchLimit
			return this.commandDeleteBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}
		// 1. 参数准备与处理：将 feiShuBatchDTO 与 当前 table 的查询结果进行比较, 生成需要新增的数据、需要更新的数据、需要删除的数据
		String result = "";
		// 1.1 查询当前 table 的 全部数据, 封装成 FeiShuQueryFieldsVO 对象
		List<JSONObject> jsonObjects = this.commandQueryBatch(appToken, tableId, null);
		List<FeiShuQueryFieldsVO> fromQuery = new ArrayList<>();
		jsonObjects.forEach(j -> fromQuery.add(j.toJavaObject(FeiShuQueryFieldsVO.class)));
		// 1.2 将 feiShuBatchDTO 转换成 List<FeiShuFieldsDTO> 对象
		List<FeiShuFieldsDTO> fromDTO = new ArrayList<>();
		records.forEach(r -> fromDTO.add(JSONObject.parseObject(JSONObject.toJSONString(r), FeiShuFieldsDTO.class)));
		// 需要新增的数据、需要更新的数据、需要删除的数据
		List<FeiShuFieldsDTO> needAdd = new ArrayList<>();
		List<FeiShuUpdateFieldsDTO> needUpdate = new ArrayList<>();
		List<String> needDelete = new ArrayList<>();

		// 1.3 遍历 fromDTO, 与 fromQuery 进行比较, 生成需要新增的数据、需要更新的数据
		// 当 fromDTO 与 fromQuery 均存在 相应 key 的值, 说明需要更新, 将当前q的 record_id 赋值给needUpdate
		fromDTO.forEach(
			d -> {
				// 1.3.1 获取当前记录的 key 值
				Object o = d.getFields().get(key);
				if (Func.isNull(o)) {
					return;
				}
				String currentKey = d.getFields().get(key).toString();
				// 1.3.2 遍历 fromQuery, 若存在当前记录的 key 值, 说明需要更新, 将当前q的 record_id 赋值给needUpdate
				boolean needUpdateFlag = false;
				for (FeiShuQueryFieldsVO q : fromQuery) {
					String queryKey = Func.isNull(q.getFields().get(key)) ? "" : q.getFields().get(key).toString();
					if (currentKey.equals(queryKey)) {
						needUpdateFlag = true;
						FeiShuUpdateFieldsDTO tempFeiShuUpdateFieldsDTO = new FeiShuUpdateFieldsDTO();
						tempFeiShuUpdateFieldsDTO.setRecord_id(q.getRecord_id());
						tempFeiShuUpdateFieldsDTO.setFields(d.getFields());
						needUpdate.add(tempFeiShuUpdateFieldsDTO);
						break;
					}
				}
				// 1.3.3 若不存在当前记录的 key 值, 说明需要新增, 将当前q的 record_id 赋值给needAdd
				if (!needUpdateFlag) {
					needAdd.add(d);
				}
			}
		);

		// 1.4 遍历 fromQuery, 与 fromDTO 进行比较, 生成需要删除的数据
		fromQuery.forEach(
			q -> {
				// 1.4.1 获取当前记录的 key 值
				String currentKey = Func.isNull(q.getFields().get(key)) ? "" : q.getFields().get(key).toString();
				// 1.4.2 遍历 fromDTO, 若不存在当前记录的 key 值, 说明需要删除, 将当前q的 record_id 赋值给needDelete
				boolean needDeleteFlag = true;
				for (FeiShuFieldsDTO f : fromDTO) {
					Object o = f.getFields().get(key);
					if (Func.isNull(o)) {
						return;
					}
					String dtoKey = f.getFields().get(key).toString();
					if (currentKey.equals(dtoKey)) {
						needDeleteFlag = false;
						break;
					}
				}
				if (needDeleteFlag) {
					needDelete.add(q.getRecord_id());
				}
			}
		);

		// 2. 需要新增的数据, 调用 commandAddBatchLimit
		if (!Func.isEmpty(needAdd)) {
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(needAdd);
			result = this.commandAddBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}

		// 3. 需要更新的数据, 调用 commandUpdateBatchLimit
		if (!Func.isEmpty(needUpdate)) {
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(needUpdate);
			result = this.commandUpdateBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}

		// 4. 需要删除的数据, 调用 commandDeleteBatchLimit
		if (!Func.isEmpty(needDelete)) {
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(needDelete);
			result = this.commandDeleteBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}
		return result;
	}

	/**
	 * 批量新增接口
	 *
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId  具体表格 id
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public String commandAddBatch(String appToken, String tableId, String bodyJson) {
		// 1. 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/";
		url += appToken;
		url += "/tables/";
		url += tableId;
		url += "/records/batch_create";
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 2. 发送请求
		String result = HttpRequest.post(url)
			.header("Authorization", authorization)
			.header("Content-Type", "application/json;charset=UTF-8")
			.body(bodyJson)
			.execute()
			.body();
		log.info("result: {}", result);
		// 3. 解析响应结果
		JSONObject jsonObject = JSONObject.parseObject(result);
		if ("0".equals(jsonObject.getString("code"))) {
			return "Success! Code: " + jsonObject.getString("code");
		}
		return jsonObject.getString("code");
	}

	/**
	 * 批量删除接口
	 *
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId  具体表格 id
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public String commandDeleteBatch(String appToken, String tableId, String bodyJson) {
		// 1. 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/";
		url += appToken;
		url += "/tables/";
		url += tableId;
		url += "/records/batch_delete";
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 2. 发送请求
		String result = HttpRequest.post(url)
			.header("Authorization", authorization)
			.header("Content-Type", "application/json;charset=UTF-8")
			.body(bodyJson)
			.execute()
			.body();
		// 3. 解析响应结果
		JSONObject jsonObject = JSONObject.parseObject(result);
		if ("0".equals(jsonObject.getString("code"))) {
			return "Success! Code: " + jsonObject.getString("code");
		}
		return jsonObject.getString("msg");
	}

	/**
	 * 批量查询接口
	 *
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId  具体表格 id
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public List<JSONObject> commandQueryBatch(String appToken, String tableId, String bodyJson) {
		// 1. 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/";
		url += appToken;
		url += "/tables/";
		url += tableId;
		url += "/records";
		url += "?";
		url += "page_size=100";
		return queryBatch(url);
	}



	/**
	 * 批量更新接口
	 *
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId  具体表格 id
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public String commandUpdateBatch(String appToken, String tableId, String bodyJson) {
		// 1. 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/";
		url += appToken;
		url += "/tables/";
		url += tableId;
		url += "/records/batch_update";
		url += "?";
		url += "user_id_type=open_id";
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 2. 发送请求
		String result = HttpRequest.post(url)
			.header("Authorization", authorization)
			.header("Content-Type", "application/json;charset=UTF-8")
			.body(bodyJson)
			.execute()
			.body();
		// 3. 解析响应结果
		JSONObject jsonObject = JSONObject.parseObject(result);
		if ("0".equals(jsonObject.getString("code"))) {
			return "Success! Code: " + jsonObject.getString("code");
		}
		return result;
	}

	/**
	 * 查询群组列表
	 *
	 * @return 响应结果
	 */
	@Override
	public List<JSONObject> commandQueryGroupsBatch() {
		// 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/im/v1/chats";
		url += "?";
		url += "page_size=";
		url += "20";
		return queryBatch(url);
	}

	/**
	 * 群组发送通知
	 *
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public String commandSendGroupMessage(String bodyJson) {
		// 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/im/v1/messages";
		url += "?";
		url += "receive_id_type=chat_id";
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 2. 发送请求
		String result = HttpRequest.post(url)
			.header("Authorization", authorization)
			.header("Content-Type", "application/json;charset=UTF-8")
			.body(bodyJson)
			.execute()
			.body();
		// 3. 解析响应结果
		JSONObject jsonObject = JSONObject.parseObject(result);
		if ("0".equals(jsonObject.getString("code"))) {
			return "Success! Code: " + jsonObject.getString("code");
		}
		return jsonObject.getString("msg");
	}

	/**
	 * 根据 key及更新时间 去重, 时间复杂度 O(n^2)
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId 具体表格 id
	 * @param key key
	 * @param updateTime 更新时间字段值
	 */
	@Override
	public void commandDistinctByKeyAndUpdateTime(String appToken, String tableId, String key, String updateTime) {
		// 1. 根据 appToken 及 tableId 获取当前飞书多维表格全表数据
		List<JSONObject> resultObjects = this.commandQueryBatch(appToken, tableId, null);
		List<FeiShuQueryFieldsVO> jsonObjects = new ArrayList<>();
		resultObjects.forEach(j -> jsonObjects.add(j.toJavaObject(FeiShuQueryFieldsVO.class)));
		List<FeiShuQueryFieldsVO> tempJsonObjects = new ArrayList<>();
		// 2. 遍历 jsonObjects 获取当前飞全表数据中，key 对应的 条数大于2条的数据
		for (FeiShuQueryFieldsVO jsonObject : jsonObjects) {
			Map<String, Object> fields = jsonObject.getFields();
			String currentKey = fields.get(key).toString();
			int count = 0;
			for (FeiShuQueryFieldsVO object : jsonObjects) {
				Map<String, Object> tempFields = object.getFields();
				String keyString = tempFields.get(key).toString();
				if(currentKey.equals(keyString)){
					count ++;
				}
			}
			if(count > 1){
				tempJsonObjects.add(jsonObject);
			}
		}
		if(Func.isNotEmpty(tempJsonObjects)){
			// 3. tempJsonObjects stream, 根据 key 分组, 保留 updateTime 最新的一条数据
			List<String> needDelete = new ArrayList<>();
			Map<String, List<FeiShuQueryFieldsVO>> collect
				= tempJsonObjects
				.stream()
				.collect(Collectors.groupingBy(o -> o.getFields().get(key).toString()));

			List<FeiShuQueryFieldsVO> maxObjects = collect
				.values()
				.stream()
				.map(group -> group
					.stream()
					.max(Comparator.comparing(
						f -> {
							Object o = f.getFields().get(updateTime);
							return Func.isNull(o) ? 0L : Long.parseLong(o.toString());
						}))
					.orElse(null)
				).collect(Collectors.toList());

			tempJsonObjects
				.stream()
				.filter(o -> !maxObjects.contains(o))
				.forEach(o -> needDelete.add(o.getRecord_id()));
			// 4. 调用 commandDeleteBatchLimit
			if(Func.isNotEmpty(needDelete)){
				FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
				tempFeiShuBatchDTO.setRecords(needDelete);
				this.commandDeleteBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
			}
		}
	}

	/**
	 * 上传文档素材,大小限制在20Mb以内
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @return 响应结果
	 */
	@Override
	public String commandUploadDoc(String appToken, String bodyJson, File file) {
		// 1. 必要参数准备：获取请求地址、获取TenantAccessToken
		String url = "https://open.feishu.cn/open-apis/drive/v1/medias/upload_all";
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;

		// bodyJson 转为 JSONObject
		JSONObject jsonObject = JSONObject.parseObject(bodyJson);

		// 创建 HTTP 客户端
		CloseableHttpClient httpClient = HttpClients.createDefault();

		// 创建 HTTP POST 请求
		HttpPost httpPost = new HttpPost(url);

		// 设置请求头
		httpPost.addHeader("Authorization", authorization);

		// 创建多部分实体
		MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();

		// 添加文件参数
		multipartEntityBuilder.addPart("file", new FileBody(file));

		// 添加其他参数
		Map<String, String> params = new HashMap<>();
		params.put("file_name", jsonObject.getString("file_name"));
		params.put("parent_type", jsonObject.getString("parent_type"));
		params.put("parent_node", jsonObject.getString("parent_node"));
		params.put("size", jsonObject.getString("size"));
		for (Map.Entry<String, String> entry : params.entrySet()) {
			multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue());
		}

		// 设置实体
		HttpEntity httpEntity = multipartEntityBuilder.build();
		httpPost.setEntity(httpEntity);

		// 执行请求
		CloseableHttpResponse httpResponse = null;
		try {
			httpResponse = httpClient.execute(httpPost);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// 获取响应实体
		HttpEntity responseEntity = httpResponse.getEntity();

		// 获取响应内容
		String responseContent = null;
		try {
			responseContent = EntityUtils.toString(responseEntity);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// 打印响应内容
		log.info(responseContent);

		// 关闭 HTTP 客户端
		try {
			httpClient.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// 3. 解析响应结果
		JSONObject jsonResult = JSONObject.parseObject(responseContent);
		if ("0".equals(jsonResult.getString("code"))) {
			// data 里的格式为 {"file_token":"file_token"},获取 file_token
			return jsonResult.getJSONObject("data").getString("file_token");
		}
		return null;
	}

	/**
	 * 适配飞书云文档查询接口V2
	 * 根据Text类型的自定义key，抽取成Map
	 * @param appToken 飞书应用对应文件夹的 token
	 * @param tableId 具体表格 id
	 * @param bodyJson 请求体 bodyJson String 格式
	 * @param textKey 自定义key
	 * @return 处理结果
	 */
	@Override
	public List<Map<String, String>> commandGetTextKeys(String appToken, String tableId, String bodyJson, String textKey) {
		// 0. 数据校验: 根据查询接口V2获取结果, 即将返回的结果准备
		List<JSONObject> queryResult = this.commandQueryBatchV2(appToken, tableId, bodyJson);
		List<Map<String, String>> result = new ArrayList<>();
		// 1. 遍历 queryResult, 将每个JSONObject的自定义key字段的值作为Map<String, String>的key值
		queryResult.forEach(j -> {
			// recordId 作为value
			String recordId = j.getString("record_id");

			// 在 fields 里 获取key, 需要事先保证 key 唯一
			JSONObject fields = j.getObject("fields", JSONObject.class);
			JSONArray jsonArray = fields.getJSONArray(textKey);
			if(Func.isNotEmpty(jsonArray)){
				// 1.1 获取自定义key字段的值作为Map<String, String>的key值
				Map<String, String> map = new HashMap<>();
				String mapKey = jsonArray.getJSONObject(0).getString("text");
				map.put(mapKey, recordId);
				result.add(map);
			}

		});

		return result;
	}



	/**
	 * 公共方法抽取
	 *
	 * @return 响应结果
	 */
	private List<JSONObject> queryBatch(String url) {
		String tempUrl = url;
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 目标结果集
		List<JSONObject> resultList = new ArrayList<>();
		boolean hasMore = true;
		while (hasMore) {
			String tempResult = HttpRequest.get(url)
				.header("Authorization", authorization)
				.header("Content-Type", "application/json;charset=UTF-8")
				.execute()
				.body();
			// tempResult 解析 其中的 items 数组字段
			JSONObject tempResultJsonObject = JSONObject.parseObject(tempResult);
			// 2.1 解析 items 字段
			List<JSONObject> tempResultList = new ArrayList<>();
			JSONObject tempDataJsonObject = new JSONObject();
			if ("0".equals(tempResultJsonObject.getString("code"))) {
				tempDataJsonObject = tempResultJsonObject.getJSONObject("data");
				if (!Func.isNull(tempDataJsonObject.get("items"))) {
					tempResultList = tempDataJsonObject.getJSONArray("items").toJavaList(JSONObject.class);
				}
			}
			resultList.addAll(tempResultList);
			// 2.2 解析 hasMore 字段
			if (tempDataJsonObject.containsKey("has_more")) {
				try {
					hasMore = tempDataJsonObject.getBoolean("has_more");
				}catch (Exception e) {
					log.error("hasMore字段解析失败", e);
				}
			}else{
				hasMore = false;
			}
			if (hasMore) {
				// 2.3 解析 next_page_token 字段, 且需重置 url
				url = tempUrl;
				String pageToken = tempDataJsonObject.getString("page_token");
				url += "&page_token=";
				url += pageToken;
			}
		}
		return resultList;
	}

	@Override
	public List<JSONObject> commandQueryBatchV2(String appToken, String tableId, String bodyJson) {
		// 请求url
		String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/";
		url += appToken;
		url += "/tables/";
		url += tableId;
		url += "/records/search";
		url += "?";
		url += "page_size=100";
		return queryBatchV2(url,bodyJson);
	}

	/**
	 * 公共方法抽取
	 *
	 * @return 响应结果
	 */
	private List<JSONObject> queryBatchV2(String url, String bodyJson) {
		String tempUrl = url;
		// Authorization: Bearer access_token
		String tenantAccessToken = null;
		try {
			tenantAccessToken = FeiShuUtils.getTenantAccessToken(FeiShuUtils.APPID, FeiShuUtils.APPSECRET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		String authorization = "Bearer " + tenantAccessToken;
		// 目标结果集
		List<JSONObject> resultList = new ArrayList<>();
		boolean hasMore = true;
		if(Func.isNull(bodyJson)){
			bodyJson =new JSONObject().toJSONString();
		}
		while (hasMore) {
			String tempResult = HttpRequest.post(url)
				.header("Authorization", authorization)
				.header("Content-Type", "application/json;charset=UTF-8")
				.body(bodyJson)
				.execute()
				.body();
			// tempResult 解析 其中的 items 数组字段
			JSONObject tempResultJsonObject = JSONObject.parseObject(tempResult);
			// 2.1 解析 items 字段
			List<JSONObject> tempResultList = new ArrayList<>();
			JSONObject tempDataJsonObject = new JSONObject();
			if ("0".equals(tempResultJsonObject.getString("code"))) {
				tempDataJsonObject = tempResultJsonObject.getJSONObject("data");
				if (!Func.isNull(tempDataJsonObject.get("items"))) {
					tempResultList = tempDataJsonObject.getJSONArray("items").toJavaList(JSONObject.class);
				}
			}
			resultList.addAll(tempResultList);
			// 2.2 解析 hasMore 字段
			hasMore = tempDataJsonObject.getBoolean("has_more");
			if (hasMore) {
				// 2.3 解析 next_page_token 字段, 且需重置 url
				url = tempUrl;
				String pageToken = tempDataJsonObject.getString("page_token");
				url += "&page_token=";
				url += pageToken;
			}
		}
		return resultList;
	}

	/**
	 * 公共方法抽取
	 * 获取 recordId List
	 * @return 响应结果
	 */
	private List<String> getRecordIdList(List<JSONObject> jsonObjects) {
		List<String> recordIdList = new ArrayList<>();
		jsonObjects.forEach(j -> recordIdList.add(j.getString("record_id")));
		return recordIdList;
	}

	@Override
	public void commandSendGroupMessage(String appToken, String tableId, String message) {
		List<JSONObject> targetGroupObjects = this.commandQueryGroupsBatch();
		List<String> chatIds = new ArrayList<>();
		targetGroupObjects.forEach(t -> chatIds.add(t.getString("chat_id")));
		// 给所有群组均发送消息
		chatIds.forEach(c -> {
			// 组装数据
			JSONObject bodyJson = new JSONObject();
			bodyJson.put("receive_id", c);
			bodyJson.put("msg_type", "text");
			HashMap<String, String> contentMap = new HashMap<>(2);
			contentMap.put("text", message);
			// 将 contentMap 转换成 JSON 格式
			bodyJson.put("content", JSON.toJSONString(contentMap));
			this.commandSendGroupMessage(bodyJson.toJSONString());
		});
	}

	@Override
	public SearchAppTableRecordResp getRecordsByCondition(Client client, String appToken, String tableId, SearchAppTableRecordReqBody searchAppTableRecordReqBody, String pageToken) {
		// 创建请求对象
		SearchAppTableRecordReq nextReq = SearchAppTableRecordReq.newBuilder()
			.appToken(appToken)
			.tableId(tableId)
			.searchAppTableRecordReqBody(searchAppTableRecordReqBody)
			.pageToken(pageToken)
			.build();
		// 发起请求
		SearchAppTableRecordResp resp = null;
		try {
			resp = client.bitable().appTableRecord().search(nextReq);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		// 处理服务端错误
		if(!resp.success()) {
			String msg = String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId());
			throw new ServiceException(msg);
		}
		return resp;
	}

	@Override
	public BatchUpdateAppTableRecordResp updateRecordsBatch(Client client, BatchUpdateAppTableRecordReq updateReq) {
		// 发起请求
		BatchUpdateAppTableRecordResp resp = null;
		try {
			resp = client.bitable().appTableRecord().batchUpdate(updateReq);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		// 处理服务端错误
		if(!resp.success()) {
			String msg = String.format("code:%s,msg:%s,reqId:%s", resp.getCode(), resp.getMsg(), resp.getRequestId());
			return resp;
		}
		return resp;
	}

	@Override
	public String commandCompareAddBatch(String appToken, String tableId, FeiShuBatchDTO feiShubatchDTO, String key) {
		// 0. 参数校验, appToken、tableId、key 必须有值, 否则直接return
		if (Func.isNull(appToken) || Func.isNull(tableId) || Func.isNull(key) || Func.isNull(feiShubatchDTO)) {
			return "appToken、tableId、key must not be null!";
		}
		// 0.1 records 为空, 说明全量删除, 处理数据并 调用 commandDeleteBatchLimit
		List<?> records = feiShubatchDTO.getRecords();
		if (Func.isEmpty(records)) {
			// 0.1.1 查询全部数据
			List<JSONObject> jsonObjects = this.commandQueryBatch(appToken, tableId, null);
			// 0.1.2 遍历并获取 jsonObjects 的 封装成 FeiShuBatchDTO 对象
			List<String> r = new ArrayList<>();
			jsonObjects.forEach(j -> r.add(j.getString("record_id")));
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(r);
			// 0.1.3 调用 commandDeleteBatchLimit
			return this.commandDeleteBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}
		// 1. 参数准备与处理：将 feiShuBatchDTO 与 当前 table 的查询结果进行比较, 生成需要新增的数据、需要更新的数据、需要删除的数据
		String result = "";
		// 1.1 查询当前 table 的 全部数据, 封装成 FeiShuQueryFieldsVO 对象
		List<JSONObject> jsonObjects = this.commandQueryBatch(appToken, tableId, null);
		List<FeiShuQueryFieldsVO> fromQuery = new ArrayList<>();
		jsonObjects.forEach(j -> fromQuery.add(j.toJavaObject(FeiShuQueryFieldsVO.class)));
		// 1.2 将 feiShuBatchDTO 转换成 List<FeiShuFieldsDTO> 对象
		List<FeiShuFieldsDTO> fromDTO = new ArrayList<>();
		records.forEach(r -> fromDTO.add(JSONObject.parseObject(JSONObject.toJSONString(r), FeiShuFieldsDTO.class)));
		// 需要新增的数据、需要更新的数据、需要删除的数据
		List<FeiShuFieldsDTO> needAdd = new ArrayList<>();
		List<FeiShuUpdateFieldsDTO> needUpdate = new ArrayList<>();

		// 1.3 遍历 fromDTO, 与 fromQuery 进行比较, 生成需要新增的数据、需要更新的数据
		// 当 fromDTO 与 fromQuery 均存在 相应 key 的值, 说明需要更新, 将当前q的 record_id 赋值给needUpdate
		fromDTO.forEach(
			d -> {
				// 1.3.1 获取当前记录的 key 值
				Object o = d.getFields().get(key);
				if (Func.isNull(o)) {
					return;
				}
				String currentKey = d.getFields().get(key).toString();
				// 1.3.2 遍历 fromQuery, 若存在当前记录的 key 值, 说明需要更新, 将当前q的 record_id 赋值给needUpdate
				boolean needUpdateFlag = false;
				for (FeiShuQueryFieldsVO q : fromQuery) {
					String queryKey = Func.isNull(q.getFields().get(key)) ? "" : q.getFields().get(key).toString();
					if (currentKey.equals(queryKey)) {
						needUpdateFlag = true;
						FeiShuUpdateFieldsDTO tempFeiShuUpdateFieldsDTO = new FeiShuUpdateFieldsDTO();
						tempFeiShuUpdateFieldsDTO.setRecord_id(q.getRecord_id());
						tempFeiShuUpdateFieldsDTO.setFields(d.getFields());
						needUpdate.add(tempFeiShuUpdateFieldsDTO);
						break;
					}
				}
				// 1.3.3 若不存在当前记录的 key 值, 说明需要新增, 将当前q的 record_id 赋值给needAdd
				if (!needUpdateFlag) {
					needAdd.add(d);
				}
			}
		);

		// 2. 需要新增的数据, 调用 commandAddBatchLimit
		if (!Func.isEmpty(needAdd)) {
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(needAdd);
			result = this.commandAddBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}

		// 3. 需要更新的数据, 调用 commandUpdateBatchLimit
		if (!Func.isEmpty(needUpdate)) {
			FeiShuBatchDTO tempFeiShuBatchDTO = new FeiShuBatchDTO();
			tempFeiShuBatchDTO.setRecords(needUpdate);
			result = this.commandUpdateBatchLimit(appToken, tableId, tempFeiShuBatchDTO);
		}
		return result;
	}

	/**
	 * 通用覆盖更新接口
	 * @param appToken appto
	 * @param tableId tableId
	 * @param feiShuBatchDTO 批量数据
	 * @return 执行结果
	 */
	@Override
	public String commandCoverBatch(String appToken, String tableId, FeiShuBatchDTO feiShuBatchDTO) {
		// 覆盖表格，先删除再新增
		List<JSONObject> deleteObjects = this.commandQueryBatch(appToken, tableId, null);
		// 遍历并获取 jsonObjects 的 封装成 FeiShuBatchDTO 对象
		List<String> r = new ArrayList<>();
		deleteObjects.forEach(j -> r.add(j.getString("record_id")));
		FeiShuBatchDTO deleteFeiShuBatchDTO = new FeiShuBatchDTO();
		deleteFeiShuBatchDTO.setRecords(r);
		// 直接覆盖
		this.commandDeleteBatchLimit(appToken, tableId, deleteFeiShuBatchDTO);
		// 新增
		return this.commandAddBatchLimit(appToken, tableId, feiShuBatchDTO);
	}

}
